{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：如何使用魔法方法\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "面试官想从这道题中考察面试人什么？\n",
    "魔法方法（magic methods），有时候，我们也称之为特殊方法或双下划线方法（dunder methods)，它们是 Python 面向对象编程的精髓，处处皆是。魔法方法具体有很多类，用于构造与初始化的：__init__，__new__；用于操作符的，__cmp__，__add__，等等。了解魔法方法会帮助你理解 Python 的面向对象机制，熟练使用的话，会让你的代码将更加优雅。这就不难发现面试官喜欢考这个题的原因了。\n",
    "\n",
    "答案\n",
    "最常用的几种魔法方法的使用方法：\n",
    "\n",
    "__init__构造器，当一个实例被创建的时候初始化的方法；\n",
    "__new__实例化对象调用的第一个方法，它只取下 cls 参数，并把其他参数传给__init__；\n",
    "__call__允许一个类的实例像函数一样被调用；\n",
    "__getitem__定义获取容器中指定元素的行为，相当于 self[key]；\n",
    "__getattr__定义当用户试图访问一个不存在属性的时候的行为；\n",
    "__setattr__定义当一个属性被设置的时候的行为；\n",
    "__getattribute__定义当一个属性被访问的时候的行为。\n",
    "拓展\n",
    "Python 中，魔法方法，即用双下划线开头和结尾的方法，比如__len__，__init__等等。魔法方法之所以魔法的地方在于它是被 Python 解释器直接调用，而非被我们直接调用。所以，大多数情况下，魔法方法的调用是隐式的，通过调用内置方法隐式调用魔法方法。比如调用 len(str) 就相当于调用 str.__len()__。\n",
    "\n",
    "内置的类定义了许多魔法方法，使用 dir() 函数可以查看类继承的魔术方法。例如，下面列出了 int 类中定义的所有属性和方法。\n",
    "\n",
    ">>> dir(int)\n",
    "['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']\n",
    "如你所看到的，int 类包含各种魔术方法，就是那些用双下划线包围的。例如，__add__方法是一种魔术方法，当我们使用 + 运算符将两个数字相加时会被调用该魔法方法。\n",
    "\n",
    ">>> n = 5\n",
    ">>> n + 5\n",
    "10\n",
    ">>> n.__add__(5)\n",
    "10\n",
    "当你做 n+5 的加法运算的时候，+ 操作符调用的就是__add__魔法方法。但是，如前所述，魔术方法并不是要直接调用，而是要通过其他方法或动作在内部调用。\n",
    "\n",
    "在面向对象编程中，如果你想自定义的类中重载某个内置运算符的行为，那么就必须重写相对应的魔法方法。我们写一个最简单的字符类：\n",
    "\n",
    "class String:\n",
    "    # 初始化\n",
    "    def __init__(self, string):\n",
    "        self.string = string\n",
    "\n",
    "    # 打印对象\n",
    "    def __repr__(self):\n",
    "        return 'Object: {}'.format(self.string)\n",
    "\n",
    "# 测试代码\n",
    "if __name__ == '__main__':\n",
    "    # 创建对象，相当于调用string1 = String.__init__('Hello')\n",
    "    string1 = String('Hello')\n",
    "    # 打印对象, 就相当于调用`string1.__repr__()`。\n",
    "    print(string1)\n",
    "    # 连接一个自定义字符对象和一个内置的字符对象\n",
    "    print(string1 +' world')\n",
    "输出结果：\n",
    "\n",
    "Object: Hello\n",
    "TypeError: unsupported operand type(s) for +: 'String' and 'str'\n",
    "为什么会报 TypeError？因为 String 类并不支持合并操作，如果我们要支持合并就必须重写__add__方法。\n",
    "\n",
    "试试按下面去定义 String 类，看看会发生什么？\n",
    "\n",
    "class String:\n",
    "    # 初始化\n",
    "    def __init__(self, string):\n",
    "        self.string = string\n",
    "\n",
    "    # 打印对象\n",
    "    def __repr__(self):\n",
    "        return 'Object: {}'.format(self.string)\n",
    "\n",
    "    # 合并其它\n",
    "    def __add__(self, other):\n",
    "        return self.string + other\n",
    "魔法方法可用于用户定义的对象模拟内置类型的行为。魔法方法是 Python 的核心功能，应根据需要使用，最好不要尝试自己写魔法方法，更多的内置的魔法方法请参考官方文档。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
